package network;

import java.io.IOException;
import java.util.LinkedList;
import java.util.Observable;
import java.util.Observer;
import java.util.Queue;
import java.util.concurrent.locks.ReentrantLock;

import link.LinkInterface;

public class Network extends Observable implements Observer {

	protected RoutingTable routingtable;
	protected TPA ownAdress;

	protected Queue<NetworkPacket> packetQueue;
	protected ReentrantLock queueLock = new ReentrantLock();

	/**
	 * Creates a network layer with a routingtable which knows the user's own
	 * adress. Contains a packetQueue off which the Transportlayer can take
	 * byte[]'s containing data.
	 * 
	 * @param adress
	 *            the TPA of the Network layer
	 * @param route
	 *            the routingtable delivered by the application.
	 * @require adress != null
	 * @require route.rout(adress) !null;
	 * */
	public Network(TPA adress, RoutingTable route) {
		routingtable = route;
		ownAdress = adress;
		packetQueue = new LinkedList<NetworkPacket>();
	}

	/**
	 * Sends a packet to the Linklayer. Firstly it creates a NetworkPacket, with
	 * the required headers secondly sends the packet. *
	 * 
	 * @param packet
	 *            the byte[] containing data.
	 * @param adress
	 *            the adress to which the data should be sent
	 * @throws IOException
	 * @require adress != null
	 * @require packet.length >= 1
	 */
	public void send(byte[] packet, TPA adress) throws IOException {

		NetworkPacket toSend = new NetworkPacket(ownAdress, adress, packet);
		sendPacket(toSend);
	}

	/**
	 * Sends a NetworkPacket to the Linklayer. It routes the Link to which the
	 * packet must be sent. Finally it sends the packet to the required Link.
	 * 
	 * @param packet
	 *            the NetworkPacket that has to be sent
	 * @throws IOException
	 */
	public void sendPacket(NetworkPacket packet) throws IOException {
		TPA adress = packet.getToTPA();
		LinkInterface toLink = routingtable.rout(adress);

		toLink.send(packet.toByteArray());

	}

	public TPA getOwnAdress() {
		return ownAdress;
	}

	/**
	 * Retrieves a packet from the queue. Used by the transportlayer to get the
	 * required packets
	 * 
	 * @return the next Networkpacket in the queue.
	 */
	public NetworkPacket getPacket() {
		NetworkPacket ret;
		queueLock.lock();
		ret = packetQueue.poll();
		queueLock.unlock();

		return ret;
	}

	/**
	 * Is called upon when a tunnel gets data. Checks whether the data has to be
	 * sent to another link or whether the data has to be sent to the Transport
	 * layer. When the adress is its own adress it will add the data to its
	 * queue and will notify its observers.
	 * 
	 * @require tunnel != null
	 */
	public void update(Observable tunnel, Object arg1) {

		byte[] pack;
		while ((pack = ((LinkInterface) tunnel).getPacket()) != null) {

			NetworkPacket packet = NetworkPacket.fromByteArray(pack);
			if (packet.checkChecksum()) {
				if (packet.getToTPA().equals(ownAdress)) {

					queueLock.lock();
					packetQueue.add(packet);
					queueLock.unlock();
					setChanged();
					notifyObservers();
				} else {
					try {
						sendPacket(packet);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

}
